Pre-populating Related Tables in Vapor
Let’s say we have two related tables: Heat
and
Technique
:
Heat ↤⇉ Technique
Their relation is one - to - many, so for one heat (dry, moist) there are many cooking techniques. But vice versa does not apply.
If we want to pre-populate Technique
table, we first need to have
Heat
table populated.
We also need a Heat.ID
for each
Technique
entity.
Let’s assume that we have the Heat
table populated, and that the
Technique
data looks something like this:
static let techniques = [
"moist" : [
(name: "sweat", desc: "To cook in fat without browning."),
(name: "braise", desc: "To cook in a small amount of liquid, after browning"),
(name: "steam", desc: "To cook foods by exposing directly to steam.")
],
"dry" : [
(name: "grill", desc: "An underneath cooking method on an open grid."),
(name: "saute", desc: "To cook quickly with high heat in a small amount of fat"),
(name: "roast", desc: "To cook foods by surrounding them by, hot, dry air.")
],
"raw" : [
(name: "raw", desc: "To consume raw.")
]
]
- First we need to fetch the the
ID
of
Heat
entity, by it’s name.
- Then insert all
Technique
entities into the database, with the
Heat.ID
.
- We also need to do the reverse.
Fetching the parent’s ID
static func getHeatID(on connection: PostgreSQLConnection, heatName: String) -> Future<Heat.ID> {
do {
// First look up the heat by its name
return try Heat.query(on: connection)
.filter(\Heat.name == heatName)
.first()
.map(to: Heat.ID.self) { heat in
guard let heat = heat else {
throw FluentError(
identifier: "PopulateTechniques_noSuchHeat",
reason: "No heat named \(heatName) exists!",
source: .capture()
)
}
// Once we have found the heat, return it's id
return heat.id!
}
}
catch {
return connection.eventLoop.newFailedFuture(error: error)
}
}
Add entities
static func addTechniques(on connection: PostgreSQLConnection, toHeatWithName heatName: String, heatTechniques: [(name: String, desc: String)]) -> Future<Void> {
// Look up the heat's ID
return getHeatID(on: connection, heatName: heatName)
.flatMap(to: Void.self) { heatID in
// Add each technique to the heat
let futures = heatTechniques.map { touple -> EventLoopFuture<Void> in
// Insert the Technique
let name = touple.0
let desc = touple.1
return Technique(name: name, description: desc, heatID: heatID)
.create(on: connection)
.map(to: Void.self) { _ in return }
}
return Future<Void>.andAll(futures, eventLoop: connection.eventLoop)
}
}
Remove entities
static func deleteTechniques(on connection: PostgreSQLConnection, forHeatWithName heatName: String, heatTechniques: [(name: String, desc: String)]) -> Future<Void> {
// Look up the heat's ID
return getHeatID(on: connection, heatName: heatName)
.flatMap(to: Void.self) { heatID in
// Delete each technique from the heat
let futures = try heatTechniques.map { touple -> EventLoopFuture<Void> in
// DELETE the technique if it exists
let name = touple.0
return try Technique.query(on: connection)
.filter(\.heatID == heatID)
.filter(\.name == name)
.delete()
}
return Future<Void>.andAll(futures, eventLoop: connection.eventLoop)
}
}
Implement Migration Requirements
static func prepare(on connection: PostgreSQLConnection) -> Future<Void> {
// Insert all heats from techniques
let futures = techniques.map { heatName, techniqueTouples in
return addTechniques(on: connection, toHeatWithName: heatName, heatTechniques: techniqueTouples)
}
return Future<Void>.andAll(futures, eventLoop: connection.eventLoop)
}
static func revert(on connection: PostgreSQLConnection) -> Future<Void> {
let futures = techniques.map { heatName, techniqueTouples in
return deleteTechniques(on: connection, forHeatWithName: heatName, heatTechniques: techniqueTouples)
}
return Future<Void>.andAll(futures, eventLoop: connection.eventLoop)
}
Prev: Pre-populating a Database with Data in Vapor
Next: Generic Migrations in Fluent
#pub #vapor #postgresql #ddl #one-to-many #fluent #many-to-one #migration #db #one-to-many #many-to-one #pre-populate